package com.ldj.tim.server.handle;

import com.ldj.tim.common.constant.Constants;
import com.ldj.tim.common.exception.TIMException;
import com.ldj.tim.common.kit.HeartBeatHandler;
import com.ldj.tim.common.pojo.BasicMessage;
import com.ldj.tim.common.pojo.TIMUserInfo;
import com.ldj.tim.common.protocol.TIMReqMsg;
import com.ldj.tim.common.util.NettyAttrUtil;
import com.ldj.tim.server.kit.ServerHeartBeatHandlerImpl;
import com.ldj.tim.server.kit.RouteHandler;
import com.ldj.tim.server.util.OfflineMessageUtil;
import com.ldj.tim.server.util.SessionSocketHolder;
import com.ldj.tim.server.util.SpringBeanFactory;
import io.netty.channel.*;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import org.hibernate.validator.internal.constraintvalidators.bv.time.futureorpresent.FutureOrPresentValidatorForReadableInstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.sql.Struct;
import java.util.Set;

/**
 * 所有的连接共用这个单例对象
 * 若不加，多个客户端来连接时服务端会报错
 */
@ChannelHandler.Sharable
public class TIMServerHandle extends SimpleChannelInboundHandler<TIMReqMsg> {

    private final static Logger LOGGER = LoggerFactory.getLogger(TIMReqMsg.class);

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        LOGGER.info("channel active");
    }

    /**
     * 取消绑定
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        //可能出现业务判断离线后再次触发 channelInactive
        TIMUserInfo userInfo = SessionSocketHolder.getUserId((NioSocketChannel) ctx.channel());
        if (userInfo != null) {
            LOGGER.warn("[{}] trigger channelInactive offline!", userInfo.getUserName());

            //Clear route info and offline.
            RouteHandler routeHandler = SpringBeanFactory.getBean(RouteHandler.class);
            routeHandler.userOffLine(userInfo, (NioSocketChannel) ctx.channel());

            ctx.channel().close();
        }
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
            // 在指定时间内没有接收到数据。
            if (idleStateEvent.state() == IdleState.READER_IDLE) {
                TIMUserInfo userInfo = SessionSocketHolder.getUserId((NioSocketChannel) ctx.channel());
                System.out.println("定时检测客户端是否存活:" + userInfo.getUserName());
                HeartBeatHandler heartBeatHandler = SpringBeanFactory.getBean(ServerHeartBeatHandlerImpl.class);
                heartBeatHandler.process(ctx);
            }
        }
        super.userEventTriggered(ctx, evt);
    }


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TIMReqMsg msg) throws Exception {
        LOGGER.info("received msg=[{}]", msg.toString());
        if (msg.getType() == Constants.CommandType.LOGIN) {
            //保存客户端与 Channel 之间的关系
            SessionSocketHolder.put(msg.getRequestId(), ctx.channel());
            SessionSocketHolder.saveSession(msg.getRequestId(), msg.getReqMsg());
            OfflineMessageUtil offlineMessageUtil = SpringBeanFactory.getBean("offlineMessageUtil",
                    OfflineMessageUtil.class);
            //拉取离线消息
            Set<String> senderIds = offlineMessageUtil.getOfflineSenderId(msg.getRequestId());
            if (!senderIds.isEmpty()){
                String message = "在您离线期间有下述用户发来离线消息：" + String.join(",", senderIds);
                TIMReqMsg protocol = new TIMReqMsg(msg.getRequestId(), message, Constants.CommandType.MSG);
                ChannelFuture future = ctx.writeAndFlush(protocol);
                future.addListener((ChannelFutureListener) channelFuture ->
                        LOGGER.info("server push msg:[{}]", protocol));
            }
        }

        //心跳更新时间
        if (msg.getType() == Constants.CommandType.PING) {
            NettyAttrUtil.updateReaderTime(ctx.channel(), System.currentTimeMillis());
            //向客户端响应 pong 消息
            TIMReqMsg heartBeat = SpringBeanFactory.getBean("heartBeat",
                    TIMReqMsg.class);
            ctx.writeAndFlush(heartBeat).addListeners((ChannelFutureListener) future -> {
                if (!future.isSuccess()) {
                    LOGGER.error("IO error,close Channel");
                    future.channel().close();
                }
            });
        }

    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        if (TIMException.isResetByPeer(cause.getMessage())) {
            return;
        }

        LOGGER.error(cause.getMessage(), cause);

    }

}
